package app_kvClient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import logger.LogSetup;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import common.messages.KVMessage;
import client.KVStore;

/**
 * This is the user interface of client
 * 
 * @author Zhou, Shi, Huang
 * 
 */
public class KVClient {

    private BufferedReader br;
    private KVStore cs;
    private String key = "";
    private String value = "";
    private String ori_command = "";
    private int command = 0;
    private int port = 0;
    private String ipAddress = null;
    private boolean isPortAvailable = false;
    private boolean isConnected = false;
    private Logger log;
    private String logLevel = "";

    private static final int CONNECT_COMMAND = 1;
    private static final int PUT_COMMAND = 2;
    private static final int GET_COMMAND = 3;
    private static final int DISCONNECT_COMMAND = 4;
    private static final int LOGLEVEL_COMMAND = 5;
    private static final int QUIT_COMMAND = 6;
    private static final int ERROR_COMMAND = 7;
    private static final int HELP_COMMAND = 8;
    private static final int RETURN_COMMAND = 9;
    private static final String PROMPT = "EchoClient> ";

    /**
     * Constructor
     * 
     * @throws IOException
     */
    public KVClient() {
        cs = new KVStore(ipAddress, port);

        log = Logger.getLogger(KVStore.class);
        PropertyConfigurator.configure("logs/log4j.properties");
    }

    /**
     * Start the human interface
     * 
     * @throws Exception
     */
    public void run() {
        System.out
                .println(PROMPT
                        + "Welcome to Echo Client, please type in command or type help!");

        while (true) {
            System.out.print(PROMPT);
            command = readCommand();
            switch (command) {
            case CONNECT_COMMAND:
                try {
                    if (!isConnected) {
                        if (port > 65535) {
                            System.out
                                    .println(PROMPT
                                            + "port is too big to be out of range-65535:"
                                            + port);
                            break;
                        }
                        cs = new KVStore(ipAddress, port);
                        cs.connect();
                        isConnected = true;
                        // byte[] bt = cs.receive();
                        // String str = new String(bt);
                        // System.out.print("EchoClient> " + str);

                        // start monitor thread
                        // SocketMonitor monitor = new SocketMonitor();
                        // Thread t = new Thread(monitor);
                        // t.start();
                    } else {
                        System.out
                                .println(PROMPT
                                        + " You have already connected, please disconnect first!");
                    }

                } catch (IllegalArgumentException e) {
                    System.out.println(PROMPT + "Please check your input.");
                    break;
                } catch (NoRouteToHostException e) {
                    System.out
                            .println(PROMPT
                                    + "No network connection, please check your network");
                    break;
                } catch (ConnectException e) {
                    System.out.println(PROMPT + "Couldn't connect to server");
                    break;
                } catch (SocketException e) {
                    System.out
                            .println(PROMPT
                                    + "There may be some unkown communication errors, please check your network");
                    break;
                } catch (SocketTimeoutException e) {
                    System.out
                            .println(PROMPT
                                    + "Time out for 5 seconds, please check your the address&port");
                    break;
                } catch (UnknownHostException e) {
                    System.out.println(PROMPT
                            + "There seems like some problem for the host, "
                            + "please try another ip addrwss");
                    break;
                } catch (IOException e) {
                    System.out.println(PROMPT
                            + "There may be some communication errors");
                    break;
                } catch (Exception e) {
                    System.exit(-1);
                    break;
                }
                log.info("Success to connect to the echo server!");
                break;
            case PUT_COMMAND:
                if (isConnected) {
                    // if (!isPortAvailable) {
                    // System.out.println(PROMPT + "No network");
                    // break;
                    // }
                    try {
                        KVMessage kvm = cs.put(key, value);
                        if (kvm.getStatus().toString()
                                .equalsIgnoreCase("PUT_SUCCESS")) {
                            System.out.println(PROMPT + "PUT SUCCESS < "
                                    + kvm.getKey() + " , " + kvm.getValue()
                                    + " >");
                        } else if (kvm.getStatus().toString()
                                .equalsIgnoreCase("PUT_UPDATE")) {
                            System.out.println(PROMPT + "UPDATE SUCCESS < "
                                    + kvm.getKey() + " , " + kvm.getValue()
                                    + " >");
                        } else if (kvm.getStatus().toString()
                                .equalsIgnoreCase("DELETE_SUCCESS")) {
                            System.out.println(PROMPT + "DELETE SUCCESS < "
                                    + kvm.getKey() + " >");
                        } else if (kvm.getStatus().toString()
                                .equalsIgnoreCase("DELETE_ERROR")) {
                            System.out.println(PROMPT + "DELETE ERROR < "
                                    + kvm.getKey() + " >");
                        } else {
                            System.out.println(PROMPT + "PUT ERROR < "
                                    + kvm.getKey() + " , " + kvm.getValue()
                                    + " >");
                        }

                    } catch (SocketException e) {
                        System.out
                                .println(PROMPT
                                        + "There may be some unkown communication errors");
                        break;
                    } catch (IOException e) {
                        System.out.println(PROMPT
                                + "There may be some communication errors");
                        break;
                    } catch (Exception e) {
                        System.out.println(PROMPT
                                + "There may be some communication errors");
                        break;
                    }
                    log.info("Success to send message: key: " + key + "value: "
                            + value);
                } else {
                    System.out
                            .println(PROMPT
                                    + "You havn't connected! Using command \"help\" to get help!");
                    log.error("Send message failed! Have not connected to the echo server!");
                }
                break;
            case GET_COMMAND:
                if (isConnected) {
                    // if (!isPortAvailable) {
                    // System.out.println(PROMPT + "No network");
                    // break;
                    // }
                    try {
                        KVMessage kvm = cs.get(key);
                        if (kvm.getStatus().toString().toLowerCase()
                                .contains("success")) {
                            System.out.println(PROMPT
                                    + kvm.getStatus().toString()
                                    + " Value is: " + kvm.getValue());
                        } else {
                            System.out.println(PROMPT + kvm.getStatus());
                        }
                    } catch (SocketException e) {
                        System.out
                                .println(PROMPT
                                        + "There may be some unkown communication errors");
                        break;
                    } catch (IOException e) {
                        System.out.println(PROMPT
                                + "There may be some communication errors");
                        break;
                    } catch (Exception e) {
                        System.out.println(PROMPT
                                + "There may be some communication errors");
                        break;
                    }
                    log.info("Success to send message:" + key);
                } else {
                    System.out
                            .println(PROMPT
                                    + "You havn't connected! Using command \"help\" to get help!");
                    log.error("Send message failed! Have not connected to the echo server!");
                }
                break;
            case DISCONNECT_COMMAND:
                try {
                    // if (!isPortAvailable) {
                    // System.out.println(PROMPT + "No network");
                    // break;
                    // }
                    if (isConnected) {
                        cs.disconnect();
                        isConnected = false;
                        System.out.println(PROMPT
                                + "Connection terminated:"
                                + cs.getClientSocket().getSocket()
                                        .getInetAddress() + "/"
                                + cs.getClientSocket().getSocket().getPort());
                        log.info("Disconnect with echo server!");
                    } else {
                        System.out
                                .println(PROMPT
                                        + "You havn't connected! Using command \"help\" to get help!");
                        log.warn("Disconnect failed! Have not connected to the echo server!");
                    }
                } catch (NullPointerException e) {
                    System.out
                            .println(PROMPT
                                    + "Please connect firstly, Use command \"help\" to get help!");
                    ;
                    break;
                } catch (Exception e) {
                    System.exit(-1);
                    break;
                }
                break;
            case LOGLEVEL_COMMAND:
                if (LogSetup.isValidLevel(logLevel)) {
                    String level = setLevel(logLevel);
                    if (logLevel.equals(LogSetup.UNKNOWN_LEVEL)) {
                        System.out
                                .println(PROMPT
                                        + "Error! No valid log level! Use command \"help\" to get help!");
                    } else {
                        System.out.println(PROMPT
                                + "Log level changed to level " + level);
                    }
                } else {
                    System.out
                            .println(PROMPT
                                    + "Error! No valid log level! Use command \"help\" to get help!");
                }
                break;
            case QUIT_COMMAND:
                // if (!isPortAvailable) {
                // System.out.println(PROMPT + "No network");
                // break;
                // }
                log.info("Quit Application!");
                System.out.println(PROMPT + "Application exit!");
                System.exit(-1);
                break;
            case ERROR_COMMAND:
                System.out.println(PROMPT
                        + "Error command! Using command \"help\" to get help!");
                log.warn("Error command!" + ori_command);
                break;
            case HELP_COMMAND:
                printHelp();
                log.info("Get help!");
                break;
            case RETURN_COMMAND:
                System.out.println("EchoClient> ");
                break;
            default:
                log.warn("What happened?");
                break;

            }
        }
    }

    /**
     * Parse the command from user
     * 
     * @return the command type representing by an integer
     */
    private int readCommand() {
        br = new BufferedReader(new InputStreamReader(System.in));
        try {
            ori_command = br.readLine();
            String temp[] = ori_command.split(" ");
            if (temp[0].equalsIgnoreCase("connect")) {
                if (temp.length == 3) {
                    ipAddress = temp[1].toString();
                    port = Integer.valueOf(temp[2].toString());
                    return CONNECT_COMMAND;
                } else {
                    return ERROR_COMMAND;
                }
            } else if (temp[0].equalsIgnoreCase("put")) {
                if (temp.length == 2) {
                    key = temp[1];
                    value = "null";
                    return PUT_COMMAND;
                } else if (temp.length == 3) {
                    key = temp[1];
                    value = temp[2];
                    return PUT_COMMAND;
                } else {
                    return ERROR_COMMAND;
                }
            } else if (temp[0].equalsIgnoreCase("get")) {
                if (temp.length == 2) {
                    key = temp[1];
                    return GET_COMMAND;
                } else {
                    return ERROR_COMMAND;
                }

            } else if (temp[0].equalsIgnoreCase("disconnect")) {
                return DISCONNECT_COMMAND;
            } else if (temp[0].equalsIgnoreCase("loglevel")) {
                if (temp.length == 2) {
                    logLevel = temp[1];
                    return LOGLEVEL_COMMAND;
                } else {
                    return ERROR_COMMAND;

                }
            } else if (temp[0].equalsIgnoreCase("quit")) {
                return QUIT_COMMAND;
            } else if (temp[0].equalsIgnoreCase("help")) {
                return HELP_COMMAND;
            } else if (temp[0].isEmpty()) {
                return RETURN_COMMAND;
            } else {
                return ERROR_COMMAND;
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
            return ERROR_COMMAND;
        }
    }

    public String setLevel(String levelString) {

        if (levelString.equals(Level.ALL.toString())) {
            log.setLevel(Level.ALL);
            return Level.ALL.toString();
        } else if (levelString.equals(Level.DEBUG.toString())) {
            log.setLevel(Level.DEBUG);
            return Level.DEBUG.toString();
        } else if (levelString.equals(Level.INFO.toString())) {
            log.setLevel(Level.INFO);
            return Level.INFO.toString();
        } else if (levelString.equals(Level.WARN.toString())) {
            log.setLevel(Level.WARN);
            return Level.WARN.toString();
        } else if (levelString.equals(Level.ERROR.toString())) {
            log.setLevel(Level.ERROR);
            return Level.ERROR.toString();
        } else if (levelString.equals(Level.FATAL.toString())) {
            log.setLevel(Level.FATAL);
            return Level.FATAL.toString();
        } else if (levelString.equals(Level.OFF.toString())) {
            log.setLevel(Level.OFF);
            return Level.OFF.toString();
        } else {
            return LogSetup.UNKNOWN_LEVEL;
        }
    }

    /**
     * help information
     */
    private void printHelp() {
        StringBuilder sb = new StringBuilder();
        sb.append(PROMPT).append("ECHO CLIENT HELP (Usage):\n");
        sb.append("-----------------------------------------------------------------------------------------------\n");
        sb.append("connect <host> <port>");
        sb.append("\t Tries to establish a TCP­ connection to the storage server based on the given server address\n");
        sb.append("\t\t\t\t and the port number of the storage service.\n");
        sb.append("put <key> <value>");
        sb.append("\t Inserts a key­value pair into the storage server data structure.\n");
        sb.append("\t\t\t\t Updates (overwrites) the current value with the given value if the server already contains the specified key.\n");
        sb.append("\t\t\t\t Deletes the entry for the given key if <value> equals null.\n");
        sb.append("get <key>");
        sb.append("\t\t Retrieves the value for the given key from the storage server. \n");
        sb.append("disconnect");
        sb.append("\t\t Disconnect from the connected server. \n");
        sb.append("logLevel");
        sb.append("\t\t Sets the logger to the specified log level");
        sb.append("ALL | DEBUG | INFO | WARN | ERROR | FATAL | OFF \n");
        sb.append("quit ");
        sb.append("\t\t\t Tears down the active connection to the server and exits the program.");
        System.out.println(sb.toString());
    }

    public static void main(String[] args) {
        KVClient c = new KVClient();
        c.run();
    }

    /**
     * Thread class to listen the socket connection in realtime
     * 
     */
    class SocketMonitor extends Thread {

        @Override
        public void run() {
            while (true) {
                try {
                    cs.connect();
                    isPortAvailable = cs.getClientSocket().getSocket()
                            .isConnected();
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        return;
                    }
                } catch (NoRouteToHostException e) {
                    log.error("No network connection");
                    // cs.socket = null;
                    System.out
                            .println(" Attention, no network connection detected ");
                    System.out.println("EchoClient> ");
                    isPortAvailable = false;
                    isConnected = false;
                    return;

                } catch (IOException e) {
                    log.error("Error network connection");
                    // cs.socket = null;
                    System.out.println(" Attention, network disconnected ");
                    System.out.println("EchoClient> ");
                    isPortAvailable = false;
                    isConnected = false;
                    return;
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
}
